package cn.jinbyte.data.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.jinbyte.data.changelog.ChangeType;
import cn.jinbyte.data.changelog.annotation.ListLogProp;
import cn.jinbyte.data.changelog.annotation.EntityLogProp;
import cn.jinbyte.data.changelog.annotation.LogProp;
import cn.jinbyte.data.changelog.convert.ChangeConvert;
import cn.jinbyte.data.changelog.filter.FieldFilter;
import cn.jinbyte.data.changelog.filter.LogPropFieldFilter;
import cn.jinbyte.data.changelog.wapper.ChangeWrapper;
import cn.jinbyte.data.changelog.wapper.ListChangeWrapper;
import cn.jinbyte.data.changelog.wapper.EntityChangeWrapper;
import cn.jinbyte.data.changelog.wapper.PropChangeWrapper;
import com.google.common.collect.Lists;
import jakarta.validation.constraints.NotNull;
import lombok.NonNull;
import lombok.SneakyThrows;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.*;
import java.math.BigDecimal;
import java.util.*;

import static org.springframework.util.StringUtils.capitalize;

/**
 * 变更日志工具类，用于获取对象修改变更内容描述
 *
 * @author jintingying
 * @since 1.0
 */
@SuppressWarnings("unused")
public class ChangeLogUtils {

    private static ChangeWrapper getLogPropChangelog(final Class<?> targetClass, Object oldObj, Object newObj, Field field) {
        // 表单中一般类型字段差异的比较
        LogProp logProp = field.getAnnotation(LogProp.class);
        if (logProp == null) {
            return null;
        }
        final Object oVar = oldObj == null ? null : invokeGetter(oldObj, field.getName(), field.getType());
        final Object nVar = newObj == null ? null : invokeGetter(newObj, field.getName(), field.getType());
        if (equals(oVar, nVar, logProp.ignoreBlank())) {
            return null;
        }
        return PropChangeWrapper.builder()
                .taggetClass(targetClass)
                .propClass(field.getType())
                .propField(field)
                .oldValue(oVar)
                .newValue(nVar)
                .build();
    }

    private static ChangeWrapper getEntityLogPropChangelog(final Class<?> targetClass, Object oldObj, Object newObj, Field field) {
        // 表单中一般类型字段差异的比较
        EntityLogProp logProp = field.getAnnotation(EntityLogProp.class);
        if (logProp == null) {
            return null;
        }
        Class<?> entityType = field.getType();
        final Object oVar = oldObj == null ? null : invokeGetter(oldObj, field.getName(), field.getType());
        final Object nVar = newObj == null ? null : invokeGetter(newObj, field.getName(), field.getType());
        // Note: 受子类型的equals方法影响
        if (equals(oVar, nVar, false)) {
            return null;
        }
        // 递归比较获取子类型值的存在差异的字段
        List<ChangeWrapper> subformChangedFields = getChangelogs(entityType, oVar, nVar);
        if (CollectionUtils.isEmpty(subformChangedFields)) {
            return null;
        }
        return EntityChangeWrapper.builder()
                .taggetClass(targetClass)
                .propField(field)
                .propClass(entityType)
                .wrappers(subformChangedFields)
                .build();
    }

    @SneakyThrows
    private static List<ChangeWrapper> getListLogPropChangelog(final Class<?> targetClass, Object oldObj, Object newObj, Field field) {
        List<ChangeWrapper> changelogs = Lists.newArrayList();
        ListLogProp logProp = field.getAnnotation(ListLogProp.class);
        if (logProp == null || !ClassUtils.isAssignable(field.getType(), List.class)) {
            return changelogs;
        }
//        final Object o1 = oldObj == null ? null : invokeGetter(oldObj, field.getName(), field.getType());
//        final Object o2 = newObj == null ? null : invokeGetter(newObj, field.getName(), field.getType());
//        final List<?> oldList = o1 == null ? Collections.EMPTY_LIST : (List<?>) o1;
//        final List<?> newList = o2 == null ? Collections.EMPTY_LIST : (List<?>) o2;
        final List<?> oldList = oldObj == null
                ? Collections.EMPTY_LIST
                : invokeGetter(oldObj, field.getName(), List.class);
        final List<?> newList = newObj == null
                ? Collections.EMPTY_LIST
                : invokeGetter(newObj, field.getName(), List.class);
        if (CollUtil.isEmpty(oldList) && CollUtil.isEmpty(newList)) {
            return changelogs;
        }
        ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
        Type actualType = parameterizedType.getActualTypeArguments()[0];
        String keyFieldName = logProp.keyProp();
        Class<?> enityClass = Class.forName(actualType.getTypeName());
        Field keyField = enityClass.getDeclaredField(keyFieldName);

        // 全部删除
        if (newList.isEmpty()) {
            for (Object oldEntity : oldList) {
                List<ChangeWrapper> changeWrappers = getChangelogs(enityClass, oldEntity, null);
                changelogs.add(ListChangeWrapper.builder()
                        .taggetClass(targetClass)
                        .propField(field)
                        .changeType(ChangeType.DELETE)
                        .propClass(enityClass)
                        .changeWrappers(changeWrappers)
                        .build());
            }
        } else {
            // 遍历比较
            for (Object newEntity : newList) {
                final Object newKey = invokeGetter(newEntity, keyField.getName(), enityClass);
                // 旧集合为空说明都是新增
                if (oldList.isEmpty()) {
                    List<ChangeWrapper> changeWrappers = getChangelogs(enityClass, null, newKey);
                    changelogs.add(ListChangeWrapper.builder()
                            .taggetClass(targetClass)
                            .propField(field)
                            .propClass(enityClass)
                            .changeType(ChangeType.ADD)
                            .keyNewValue(newKey)
                            .changeWrappers(changeWrappers)
                            .build());
                    continue;
                }
                // 修改的
                boolean isUpdate = false;
                for (Object oldEntity : oldList) {
                    Object oldKey = null;
                    if (oldEntity != null) {
                        oldKey = invokeGetter(oldEntity, keyField.getName(), enityClass);
                    }
                    if (equals(oldKey, newKey, false)) {
                        isUpdate = true;
                        oldList.remove(oldEntity); // not good
                        List<ChangeWrapper> changeWrappers = getChangelogs(enityClass, oldEntity, newEntity);
                        changelogs.add(ListChangeWrapper.builder()
                                .taggetClass(targetClass)
                                .propField(field)
                                .propClass(enityClass)
                                .changeType(ChangeType.UPDATE)
                                .keyOldValue(oldKey)
                                .keyNewValue(newKey)
                                .changeWrappers(changeWrappers)
                                .build());
                        break;
                    }
                }
                if (isUpdate) {
                    continue;
                }
                // 新增的
                List<ChangeWrapper> changeWrappers = getChangelogs(enityClass, null, newEntity);
                changelogs.add(ListChangeWrapper.builder()
                        .taggetClass(targetClass)
                        .propField(field)
                        .propClass(enityClass)
                        .changeType(ChangeType.ADD)
                        .keyNewValue(newKey)
                        .changeWrappers(changeWrappers)
                        .build());
            }
            // 删除的
            for (Object oldEntity : oldList) {
                Object xVar = null;
                if (oldEntity != null) {
                    xVar = invokeGetter(oldEntity, keyField.getName(), enityClass);
                }
                List<ChangeWrapper> changeWrappers = getChangelogs(enityClass, oldEntity, null);
                changelogs.add(ListChangeWrapper.builder()
                        .taggetClass(targetClass)
                        .propField(field)
                        .propClass(enityClass)
                        .changeType(ChangeType.DELETE)
                        .keyOldValue(xVar)
                        .changeWrappers(changeWrappers)
                        .build());
            }
        }

        return changelogs;
    }


    /**
     * 比较原表单值与新表单值的差异
     *
     * @param targetClass 实体对象类
     * @param oldObj      对象旧值
     * @param newObj      对象新值
     * @return 变更内容包装类
     */
    @SneakyThrows
    @NotNull
    public static <T> List<ChangeWrapper> getChangelogs(@NonNull final Class<?> targetClass,
                                                        final T oldObj,
                                                        final T newObj,
                                                        String... ignoreProps) {
        List<ChangeWrapper> changelogs = Lists.newArrayList();
        List<Field> allFields = getTargetFields(targetClass, new LogPropFieldFilter(ignoreProps), true);
        if (CollectionUtils.isEmpty(allFields)) {
            return changelogs;
        }

        // 比较原表单值与新表单值中需要比较差异的字段的差异
        for (Field field : allFields) {
            // 表单中一般类型字段差异的比较
            LogProp logProp = field.getAnnotation(LogProp.class);
            if (logProp != null) {
                ChangeWrapper changeWrapper = getLogPropChangelog(targetClass, oldObj, newObj, field);
                if (changeWrapper != null) {
                    changelogs.add(changeWrapper);
                }
                continue;
            }
            // 表单中子表单类型差异的比较
            EntityLogProp entityLogProp = field.getAnnotation(EntityLogProp.class);
            if (entityLogProp != null) {
                ChangeWrapper changeWrapper = getEntityLogPropChangelog(targetClass, oldObj, newObj, field);
                if (changeWrapper != null) {
                    changelogs.add(changeWrapper);
                }
                continue;
            }
            // 表单中集合类型差异的比较
            ListLogProp collectionLogProp = field.getAnnotation(ListLogProp.class);
            if (collectionLogProp != null && ClassUtils.isAssignable(field.getType(), List.class)) {
                List<ChangeWrapper> changeWrappers = getListLogPropChangelog(targetClass, oldObj, newObj, field);
                changelogs.addAll(changeWrappers);
            }
        }
        return changelogs;
    }

    /**
     * 转换
     * 如性别属性 value = 1 转换为 男
     *
     * @param logProp 表单字段
     * @param value   值
     * @return 转换后的值
     */
    @SneakyThrows
    public static String convert(LogProp logProp, Object value) {
        Class<? extends ChangeConvert> convertClass = logProp.convert();
        ChangeConvert changeConvertInstance = convertClass.getDeclaredConstructor().newInstance();
        return changeConvertInstance.convert(value);
    }

    /**
     * 获取修改内容描述
     *
     * @return 修改内容描述列表
     */
    public static <T> List<String> changeDescription(@NonNull final Class<?> formClass, final T o, final T n, String... ignoreFields) {
        List<ChangeWrapper> changeWrappers = getChangelogs(formClass, o, n, ignoreFields);
        return changeDescription(changeWrappers, ChangeType.UPDATE);
    }

    public static <T> List<String> addDescription(@NonNull final Class<?> formClass, final T n, String... ignoreFields) {
        List<ChangeWrapper> changeWrappers = getChangelogs(formClass, null, n, ignoreFields);
        return changeDescription(changeWrappers, ChangeType.ADD);
    }

    public static <T> List<String> deleteDescription(@NonNull final Class<?> formClass, final T o, String... ignoreFields) {
        List<ChangeWrapper> changeWrappers = getChangelogs(formClass, o, null, ignoreFields);
        return changeDescription(changeWrappers, ChangeType.DELETE);
    }

    /**
     * 获取修改内容描述
     *
     * @param changeWrappers 修改内容包装对象列表
     * @return 修改内容描述列表
     */
    public static List<String> changeDescription(List<ChangeWrapper> changeWrappers, ChangeType cType) {
        List<String> desc = new ArrayList<>(changeWrappers.size());
        for (ChangeWrapper changeWrapper : changeWrappers) {
            if (changeWrapper instanceof PropChangeWrapper propChangeWrapper) {
                desc.add(propChangeWrapper.toLogString(cType));
            }
            if (changeWrapper instanceof EntityChangeWrapper entityChangeWrapper) {
                desc.addAll(entityChangeWrapper.toLogString(cType));
            }
            if (changeWrapper instanceof ListChangeWrapper listChangeWrapper) {
                desc.addAll(listChangeWrapper.toLogString());
            }
        }
        return desc;
    }

    public static boolean equals(Object o, Object n, boolean ignoreBlank) {
        if (o == null && n == null) {
            return true;
        }
        Class<?> clz = o != null ? o.getClass() : n.getClass();
        if (Number.class.isAssignableFrom(clz)) {
            Number numberO = o != null ? (Number) o : null;
            Number numberN = n != null ? (Number) n : null;
            return numberO instanceof BigDecimal && numberN instanceof BigDecimal ? NumberUtil.equals((BigDecimal) numberO, (BigDecimal) numberN) : Objects.equals(numberO, numberN);
        }
        if (String.class.isAssignableFrom(clz)) {
            String stringO = o != null ? (String) o : "";
            String stringN = n != null ? (String) n : "";
            if (ignoreBlank) {
                stringO = StringUtils.replace(stringO, " ", "");
                stringN = StringUtils.replace(stringN, " ", "");
            }
            return StringUtils.equals(stringO, stringN);
        }
        return Objects.equals(o, n);
    }


    /**
     * 获取所有需要比较的字段
     *
     * @param clazz              类
     * @param containsSuperClass 是否包含父类
     * @param filter             过滤器
     */
    public static List<Field> getTargetFields(@NonNull Class<?> clazz, FieldFilter filter, boolean containsSuperClass) {
        Field[] declaredFields = clazz.getDeclaredFields();
        List<Field> fields = Lists.newArrayList();
        fields.addAll(Arrays.stream(declaredFields).filter(filter::condition).toList());
        if (containsSuperClass && clazz.getSuperclass() != null && clazz != Object.class) {
            fields.addAll(getTargetFields(clazz.getSuperclass(), filter, true));
        }
        return fields;
    }

    public static List<Field> getTargetFields(@NonNull Class<?> clazz, FieldFilter filter) {
        return getTargetFields(clazz, filter, false);
    }

    /**
     * 调用Getter方法. 支持多级，如：对象名.对象名.属性名
     */
    public static <E> E invokeGetter(final Object obj, final String propertyName, Class<E> type) {
        Assert.notNull(obj, "obj can't be empty");
        Assert.hasLength(propertyName, "propertyName can't be empty");
        String[] split = propertyName.split("\\.");
        String name = split[split.length - 1];
        Assert.hasLength(name, "name can't be empty");
        String getterMethodName = "get" + capitalize(name);
        return invokeMethod(obj, getterMethodName, null, null);
    }

    /**
     * 直接调用对象方法, 无视private/protected修饰符.
     * 用于一次性调用的情况，否则应使用getAccessibleMethod()函数获得Method后反复调用. 同时匹配方法名+参数类型，
     */
    @SneakyThrows
    public static <E> E invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes, final Object[] args) {
        if (obj == null || methodName == null) {
            return null;
        }
        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
        return (E) method.invoke(obj, args);

    }

    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. 如向上转型到Object仍无法找到, 返回null. 匹配函数名+参数类型。
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
     */
    @SneakyThrows
    public static Method getAccessibleMethod(final Object obj, final String methodName, final Class<?>... parameterTypes) {
        Validate.notBlank(methodName, "methodName can't be blank");
        Class<?> searchType = obj.getClass();
        while (searchType != null) {
            try {
                Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
                makeAccessible(obj, method);
                return method;
            } catch (NoSuchMethodException ignored) {
                searchType = searchType.getSuperclass();
            }
        }
        throw new NoSuchMethodException(methodName);
    }

    /**
     * 改变private/protected的方法为public，尽量不调用实际改动的语句，避免JDK的SecurityManager抱怨。
     */
    public static void makeAccessible(final Object obj, Method method) {
        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) {
            method.setAccessible(true);
        }
    }


}
